using System;
using System.Globalization;
using System.Reflection;
using System.Web;
using System.Web.Hosting;
using System.Web.SessionState;

namespace Salient.Web.Administration
{
    public sealed class WebAdminRemotingManager : MarshalByRefObject
    {
        private static string _assemblyVersion;
        private readonly string _applicationMetaPath;
        private readonly HttpSessionState _session;
        private string _applicationPhysicalPath;
        private ApplicationManager _appManager;
        private object _configurationHelper;

        public WebAdminRemotingManager(string applicationMetaPath, string applicationPhysicalPath,
                                       HttpSessionState session)
        {
            _applicationMetaPath = applicationMetaPath;
            _applicationPhysicalPath = applicationPhysicalPath;
            _session = session;
        }

        public static string AssemblyVersionString
        {
            get
            {
                if (String.IsNullOrEmpty(_assemblyVersion))
                {
                    _assemblyVersion = "System.Web.Administration.WebAdminConfigurationHelper, " + typeof (HttpContext).Assembly.GetName();
                }
                return _assemblyVersion;
            }
        }

        private ApplicationManager AppManager
        {
            get
            {
                if (_appManager == null)
                {
                    return _appManager = ApplicationManager.GetApplicationManager();
                }
                return _appManager;
            }
        }

        protected string ApplicationMetaPath
        {
            get { return _applicationMetaPath; }
        }

        public string ApplicationPhysicalPath
        {
            get { return _applicationPhysicalPath; }
            set
            {
                _applicationPhysicalPath = value;
                // Set provider proxy references to null, to account for the edge case where ApplicationPhysicalPath is set twice.
                // Notes: this does not shut down the target appdomain, which is the desired behavior, since, in the unlikely case where the
                // ApplicationPhysicalPath is changed, the change is likely to be reverted.  Resetting the providers is necessary because
                // the existing providers point to the old target appdomain.
                ResetProviders();
            }
        }

        public object ConfigurationHelperInstance
        {
            get { return CreateConfigurationHelper(); }
        }

        private HttpSessionState Session
        {
            get { return _session; }
        }

        public string TargetAppId
        {
            get
            {
                if (Session["WebAdminTargetAppId"] != null)
                {
                    return (string) Session["WebAdminTargetAppId"];
                }
                else
                {
                    return string.Empty;
                }
            }
            set { Session["WebAdminTargetAppId"] = value; }
        }

        public override Object InitializeLifetimeService()
        {
            return null; // never expire lease
        }

        private object CreateConfigurationHelper()
        {
            if (_configurationHelper != null)
            {
                return _configurationHelper;
            }
            string appPath = ApplicationMetaPath;
            string appPhysPath = ApplicationPhysicalPath;
            string targetAppId = String.Empty;

            string typeFullName = AssemblyVersionString;
            Type tempType = Type.GetType(typeFullName);

            // Put together some unique app id
            string appId = (String.Concat(appPath, appPhysPath).GetHashCode()).ToString("x",
                                                                                        CultureInfo.InvariantCulture);

            _configurationHelper = ApplicationManager.GetApplicationManager().CreateObject(appId, tempType, appPath,
                                                                                           appPhysPath, false, true);
            TargetAppId = appId;

            return _configurationHelper;
        }

        private void EnsureTargetAppId()
        {
            if (TargetAppId != null)
            {
                return;
            }
            // In some cases, the target appdomain exists before the AppId is discovered by AppManager.CreateObjectWithDefaultAppHostAndAppId
            // (for example if the target app is already running).  In this case, retrieve one of the 
            // providers (we make an arbitrary choice to retrieve the membership provider).  This forces the object to 
            // discover the target appid.  

            CreateConfigurationHelper(); // Retrieves the target appid.
        }

        public VirtualDirectory GetVirtualDirectory(string virtualDir)
        {
            VirtualDirectory vdir = null;
            object configHelperObject = ConfigurationHelperInstance;
            if (configHelperObject != null)
            {
                string typeFullName = AssemblyVersionString;
                Type tempType = Type.GetType(typeFullName);

                BindingFlags allBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
                MethodInfo method = tempType.GetMethod("GetVirtualDirectory", allBindingFlags);
                if (method != null)
                {
                    vdir = (VirtualDirectory) method.Invoke(configHelperObject, new object[] {virtualDir});
                }
            }

            return vdir;
        }

        public void ResetProviders()
        {
            _configurationHelper = null;
            TargetAppId = null;
        }

        public void ShutdownTargetApplication()
        {
            // CONSIDER: Rather than calling EnsureTargetAppId(), is there a method on AppManager that can tell us whether
            // an appdomain exists based on the Id, and another method that creates the "default Id" from the application 
            // path and physical path?  This prevents the following two lines from creating the appdomain and immediately 
            // shutting it down in the (edge) case where the target appdomain doesn't exist.

            EnsureTargetAppId();
            AppManager.ShutdownApplication(TargetAppId);

            // ResetProviders to ensure that the value of TargetAppId is in sync with the provider proxies.  Calling 
            // property get for a provider proxy sets TargetAppId to the correct value.
            ResetProviders();
        }
    }
}